﻿using System;
using Devonline.Core;
using Devonline.Entity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace Devonline.Identity
{
    /// <summary>
    /// 重载的身份上下文, 提供字符串类型主键的默认实现
    /// </summary>
    public class IdentityDbContext : IdentityDbContext<User, Role, UserClaim, UserRole, UserLogin, RoleClaim, UserToken, Group, UserGroup, Level, Resource, AccessRule, AccessApply, AccessRecord, Parameter, Attachment, string>
    {
        public IdentityDbContext(DbContextOptions<IdentityDbContext> options) : base(options)
        {
        }
    }

    /// <summary>
    /// 重载的高级身份上下文
    /// </summary>
    /// <typeparam name="TUser"></typeparam>
    /// <typeparam name="TRole"></typeparam>
    /// <typeparam name="TUserClaim"></typeparam>
    /// <typeparam name="TUserRole"></typeparam>
    /// <typeparam name="TUserLogin"></typeparam>
    /// <typeparam name="TRoleClaim"></typeparam>
    /// <typeparam name="TUserToken"></typeparam>
    /// <typeparam name="TGroup"></typeparam>
    /// <typeparam name="TUserGroup"></typeparam>
    /// <typeparam name="TLevel"></typeparam>
    /// <typeparam name="TResource"></typeparam>
    /// <typeparam name="TAccessRule"></typeparam>
    /// <typeparam name="TAccessApply"></typeparam>
    /// <typeparam name="TAccessRecord"></typeparam>
    /// <typeparam name="TKey"></typeparam>
    public class IdentityDbContext<TUser, TRole, TUserClaim, TUserRole, TUserLogin, TRoleClaim, TUserToken, TGroup, TUserGroup, TLevel, TResource, TAccessRule, TAccessApply, TAccessRecord, TParameter, TAttachment, TKey> :
        IdentityDbContext<TUser, TRole, TKey, TUserClaim, TUserRole, TUserLogin, TRoleClaim, TUserToken>
        where TUser : User<TKey>
        where TRole : Role<TKey>
        where TUserClaim : UserClaim<TKey>
        where TUserRole : UserRole<TKey>
        where TUserLogin : UserLogin<TKey>
        where TRoleClaim : RoleClaim<TKey>
        where TUserToken : UserToken<TKey>
        where TGroup : Group<TKey>
        where TUserGroup : UserGroup<TKey>
        where TLevel : Level<TKey>
        where TResource : Resource<TKey>
        where TAccessRule : AccessRule<TKey>
        where TAccessApply : AccessApply<TKey>
        where TAccessRecord : AccessRecord<TKey>
        where TParameter : Parameter<TKey>
        where TAttachment : Attachment<TKey>
        where TKey : IEquatable<TKey>, IConvertible
    {
        public IdentityDbContext(DbContextOptions options) : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            builder.Entity<TUser>(b =>
            {
                b.HasKey(x => x.Id);
                b.ToTable(typeof(TUser).GetTableName());
                b.Property(x => x.State).HasDefaultValue(DataState.Available);
                b.Property(x => x.Type).HasDefaultValue(IdentityType.Internal);

                b.HasIndex(u => u.NormalizedUserName).HasDatabaseName("UserNameIndex").IsUnique();
                b.HasIndex(u => u.NormalizedEmail).HasDatabaseName("EmailIndex");

                b.HasMany<TUserRole>().WithOne().HasForeignKey(x => x.UserId).IsRequired();
                b.HasMany<TUserGroup>().WithOne().HasForeignKey(x => x.UserId).IsRequired();
                b.HasMany<TUserClaim>().WithOne().HasForeignKey(x => x.UserId).IsRequired();
                b.HasMany<TUserLogin>().WithOne().HasForeignKey(x => x.UserId).IsRequired();
                b.HasMany<TUserToken>().WithOne().HasForeignKey(x => x.UserId).IsRequired();
            });

            builder.Entity<TRole>(b =>
            {
                b.HasKey(x => x.Id);
                b.ToTable(typeof(TRole).GetTableName());
                b.Property(x => x.State).HasDefaultValue(DataState.Available);
                b.Property(x => x.Type).HasDefaultValue(IdentityType.Internal);

                b.HasIndex(x => x.NormalizedName).HasDatabaseName("RoleNameIndex").IsUnique();

                b.HasMany<TUserRole>().WithOne().HasForeignKey(x => x.RoleId).IsRequired();
                b.HasMany<TRoleClaim>().WithOne().HasForeignKey(x => x.RoleId).IsRequired();
                b.HasMany<TUserGroup>().WithOne().HasForeignKey(x => x.RoleId);
            });

            builder.Entity<TUserRole>(b =>
            {
                b.HasKey(x => new { x.UserId, x.RoleId });
                b.ToTable(typeof(TUserRole).GetTableName());
            });

            builder.Entity<TUserLogin>(b =>
            {
                b.HasKey(x => new { x.LoginProvider, x.ProviderKey });
                b.ToTable(typeof(TUserLogin).GetTableName());
            });

            builder.Entity<TUserToken>(b =>
            {
                b.HasKey(x => new { x.UserId, x.LoginProvider, x.Name });
                b.ToTable(typeof(TUserToken).GetTableName());
            });

            builder.Entity<TUserClaim>().ToTable(typeof(TUserClaim).GetTableName());
            builder.Entity<TRoleClaim>().ToTable(typeof(TRoleClaim).GetTableName());

            builder.Entity<TLevel>(b =>
            {
                b.Property(x => x.State).HasDefaultValue(DataState.Available);
                b.Property(x => x.Type).HasDefaultValue(IdentityType.Internal);
            });

            builder.Entity<TGroup>(b =>
            {
                b.Property(x => x.State).HasDefaultValue(DataState.Available);
                b.Property(x => x.Type).HasDefaultValue(IdentityType.Internal);
                b.HasMany<TUserGroup>().WithOne().HasForeignKey(x => x.GroupId).IsRequired();
            });

            builder.Entity<TUserGroup>(b => b.HasKey(x => new { x.UserId, x.GroupId }));

            builder.Entity<TResource>(b =>
            {
                b.Property(x => x.State).HasDefaultValue(DataState.Available);
                b.Property(x => x.Type).HasDefaultValue(IdentityType.Internal);
                b.Property(x => x.AssignType).HasDefaultValue(AssignType.User);
                b.Property(x => x.ResourceType).HasDefaultValue(ResourceType.Page);
                b.Property(x => x.AccessLevel).HasDefaultValue(AccessLevel.Internal);
            });

            builder.Entity<TAccessApply>(b =>
            {
                b.Property(x => x.State).HasDefaultValue(DataState.Available);
                b.Property(x => x.AllowType).HasDefaultValue(AllowType.Allow);
            });

            builder.Entity<TAccessRule>(b =>
            {
                b.Property(x => x.State).HasDefaultValue(DataState.Available);
                b.Property(x => x.AssignType).HasDefaultValue(AssignType.Role);
                b.Property(x => x.AllowType).HasDefaultValue(AllowType.Allow);
            });

            builder.Entity<TAccessRecord>(b => b.Property(x => x.State).HasDefaultValue(DataState.Available));
            builder.Entity<TParameter>(b => b.Property(x => x.State).HasDefaultValue(DataState.Available));
            builder.Entity<TAttachment>(b => b.Property(x => x.State).HasDefaultValue(DataState.Available));
        }

        #region identity core entities
        /// <summary>
        /// level
        /// </summary>
        public virtual DbSet<TLevel> Levels { get; set; }
        /// <summary>
        /// Group
        /// </summary>
        public virtual DbSet<TGroup> Groups { get; set; }
        /// <summary>
        /// UserGroup
        /// </summary>
        public virtual DbSet<TUserGroup> UserGroups { get; set; }
        /// <summary>
        /// Resource
        /// </summary>
        public virtual DbSet<TResource> Resources { get; set; }
        /// <summary>
        /// AccessRule
        /// </summary>
        public virtual DbSet<TAccessRule> AccessRules { get; set; }
        /// <summary>
        /// AccessApply
        /// </summary>
        public virtual DbSet<TAccessApply> AccessApplies { get; set; }
        /// <summary>
        /// AccessRecord
        /// </summary>
        public virtual DbSet<TAccessRecord> AccessRecords { get; set; }
        /// <summary>
        /// Parameter
        /// </summary>
        public virtual DbSet<TParameter> Parameters { get; set; }
        /// <summary>
        /// Attachment
        /// </summary>
        public virtual DbSet<TAttachment> Attachments { get; set; }
        #endregion
    }
}